// Array function pump, used by various apps.
// Copyright 2006 (C) Ralph Thomas

#ifndef MISSION_ARRAY_FUNCTION_ADAPTER_H
#define MISSION_ARRAY_FUNCTION_ADAPTER_H

#include <boost/type_traits/function_traits.hpp>
#include <boost/function.hpp>
#include <vector>
#include <adobe/array.hpp>
#include <adobe/name.hpp>
#include <adobe/algorithm.hpp>
#include <binder.hpp>
#include <sstream>

namespace app {
	//
	/// The fn namespace contains the implementation details for the array
	/// function adapter. 
	//
	namespace fn {
		typedef std::vector<adobe::any_regular_t> value_list_t;
		
		template<typename FUNCTION_TYPE, int ARITY>
		struct invoke {
			void operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				std::stringstream ss;
				ss << "No partial specialization for array functions with " << ARITY << " arguments -- add one to ui/array_function_adapter.h";
				throw std::runtime_error( ss.str() );
			}
		};
		template<typename FUNCTION_TYPE>
		struct invoke<FUNCTION_TYPE, 0> {
			void operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& ) {
				func();
			}
		};
		template<typename FUNCTION_TYPE>
		struct invoke<FUNCTION_TYPE, 1> {
			void operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				func(
					values[0].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>()
				);
			}
		};
		template<typename FUNCTION_TYPE>
		struct invoke<FUNCTION_TYPE, 2> {
			void operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				func(
					values[0].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>(),
					values[1].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>()
				);
			}
		};
		template<typename FUNCTION_TYPE>
		struct invoke<FUNCTION_TYPE, 3> {
			void operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				func(
					values[0].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>(),
					values[1].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>(),
					values[2].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg3_type>::type>()
				);
			}
		};
		template<typename FUNCTION_TYPE>
		struct invoke<FUNCTION_TYPE, 4> {
			void operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				func(
					values[0].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>(),
					values[1].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>(),
					values[2].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg3_type>::type>(),
					values[3].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg4_type>::type>()
				);
			}
		};
		template<typename FUNCTION_TYPE>
		struct invoke<FUNCTION_TYPE, 5> {
			void operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				func(
					values[0].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>(),
					values[1].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>(),
					values[2].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg3_type>::type>(),
					values[3].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg4_type>::type>(),
					values[4].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg5_type>::type>()
				);
			}
		};
		template<typename FUNCTION_TYPE>
		struct invoke<FUNCTION_TYPE, 6> {
			void operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				func(
					values[0].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>(),
					values[1].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>(),
					values[2].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg3_type>::type>(),
					values[3].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg4_type>::type>(),
					values[4].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg5_type>::type>(),
					values[5].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg6_type>::type>()
				);
			}
		};
		template<typename FUNCTION_TYPE>
		struct invoke<FUNCTION_TYPE, 7> {
			void operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				func(
					values[0].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>(),
					values[1].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>(),
					values[2].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg3_type>::type>(),
					values[3].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg4_type>::type>(),
					values[4].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg5_type>::type>(),
					values[5].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg6_type>::type>(),
					values[6].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg7_type>::type>()
				);
			}
		};
		template<typename FUNCTION_TYPE>
		struct invoke<FUNCTION_TYPE, 8> {
			void operator()( const boost::function<FUNCTION_TYPE>& func, const value_list_t& values ) {
				func(
					values[0].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg1_type>::type>(),
					values[1].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg2_type>::type>(),
					values[2].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg3_type>::type>(),
					values[3].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg4_type>::type>(),
					values[4].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg5_type>::type>(),
					values[5].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg6_type>::type>(),
					values[6].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg7_type>::type>(),
					values[7].cast<typename boost::remove_reference<typename boost::function_traits<FUNCTION_TYPE>::arg8_type>::type>()
				);
			}
		};
	}
	template<typename FUNCTION_TYPE>
	class array_function_adapter_t : public adobe::binder_t {
		typedef std::vector<adobe::binder_helper_t*> binder_list_t;
		typedef boost::function<FUNCTION_TYPE> function_t;
		function_t	function_m;
		binder_list_t	binders_m;
		fn::value_list_t	values_m;
		adobe::array_t	source_m;
		adobe::sheet_t&	sheet_m;
		//
		/// Called when a value in the values array should change.
		//
		void update_array( unsigned int index, const adobe::any_regular_t& value ) {
			values_m[index] = value;
		}
		void build_helper( unsigned int& index, const adobe::any_regular_t& value ) {
			adobe::binder_helper_t* binder = new adobe::binder_helper_t( sheet_m );
			binder->initialize( value, boost::bind( &array_function_adapter_t::update_array, this, index, _1 ), 0 );
			binders_m.push_back( binder );
			index++;
		}
	  public:
		array_function_adapter_t( const boost::function<FUNCTION_TYPE>& func, adobe::sheet_t& sheet, const adobe::array_t& source_array )
		: function_m( func ), source_m( source_array ), sheet_m( sheet ) {
			//
			// Verify that we have enough arguments for our function.
			//
			if( source_array.size() < (unsigned int)boost::function_traits<FUNCTION_TYPE>::arity )
				throw std::runtime_error( "insufficient arguments for function" );
			//
			// Create a binder helper for each item in the array.
			//
			values_m.resize( source_array.size() );
			unsigned int index( 0 );
			adobe::for_each( source_array,
				boost::bind( &array_function_adapter_t::build_helper, this, boost::ref( index ), _1 ) );
		}
		virtual ~array_function_adapter_t() {
			for( binder_list_t::iterator i = binders_m.begin(); i != binders_m.end(); i++ )
				delete *i;
		}
		virtual void set( const adobe::any_regular_t& val ) {
			throw std::runtime_error( "function pump doesn't contain a value to set!" );
		}
		virtual void set_signals( const adobe::sheet_t::monitor_value_t&, const adobe::sheet_t::monitor_active_t& ) {}
		virtual void substitute( const adobe::name_t& name, const adobe::any_regular_t& val ) {
			adobe::for_each( binders_m, boost::bind( &adobe::binder_helper_t::substitute, _1, name, val ) );
		}
		virtual adobe::binder_ptr_t copy() const {
			return adobe::binder_ptr_t( new array_function_adapter_t<FUNCTION_TYPE>( function_m, sheet_m, source_m ) );
		}
		virtual void invoke() {
			//
			// Now for some magic.
			//
			fn::invoke<FUNCTION_TYPE, boost::function_traits<FUNCTION_TYPE>::arity> invoker;
			invoker( function_m, values_m );
		}
		static adobe::binder_ptr_t create( const boost::function<FUNCTION_TYPE>& f, adobe::sheet_t& sheet, const adobe::array_t& source ) {
			return adobe::binder_ptr_t( new array_function_adapter_t<FUNCTION_TYPE>( f, sheet, source ) );
		}
	};
}

#endif

